﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// A class to keep track of different showtimes and which ReservedSeatsList corresponds to the showtime.
/// A new entry is to be added to the dictionary every time a new showtime is made. (Or at least when it is accessed by a customer.)
/// </summary>
public class GetReservedSeatsList
{
    private static GetReservedSeatsList _instance;
    //private GetReservedSeatsList(){}

    public static GetReservedSeatsList Instance
    {
        get 
        { 
            if (_instance == null)
            {
                _instance = new GetReservedSeatsList();
            }
            return _instance;
        }
    }

    private Dictionary<ShowTime ,ReservedSeatsList> seatsDict = new Dictionary<ShowTime ,ReservedSeatsList>();
        
    public ReservedSeatsList GetInternalSeats(ShowTime sht)
    {

        RemoveOld();
        ReservedSeatsList ret;
        //If date and room are the same, we can assume it's the right showtime.
        try
        {
            ret = seatsDict.Single(x => x.Key.CinemaRoom.Id == sht.CinemaRoom.Id && x.Key.DateTime == sht.DateTime).Value;
        }
        catch (InvalidOperationException)
        {
            ret = new ReservedSeatsList(sht);
            AddInternalSeats(sht, ret);
        }
        catch (Exception)
        {
            
            throw;
        }
        

        //Hoever, if the showtime does not exist, we can assume that we want to add it instead!
        return ret;
    }

    public void AddInternalSeats(ShowTime sht, ReservedSeatsList reservedSeatsList)
    {
        seatsDict.Add(sht, reservedSeatsList);
    }

    public void AddInternalSeats(ShowTime sht)
    {
        seatsDict.Add(sht, new ReservedSeatsList(sht));
    }

    public void RemoveInternalSeats(ShowTime sht)
    {
        //Only remove objects from the dictionary if they can no longer be used.
        if (sht.DateTime < DateTime.Now)
        {
            //Even though the references might not be exactly the same, we still want to remove the ReservedSeatsList
            //Therefore it is assumed we can remove it, if the key shares the same DateTime and CinemaRoom, as these two properties makes a showtime unique.
            seatsDict.Remove(
                seatsDict.Single(x => x.Key.DateTime == sht.DateTime && x.Key.CinemaRoom == sht.CinemaRoom).Key);
        }
    }

    //There's no point in keeping lists for showtimes that are already expired!
    private void RemoveOld()
    {
        try
        {
            seatsDict.Remove(seatsDict.Single(x => (x.Key.DateTime.Ticks > DateTime.Now.Ticks)).Key);
        }
        catch(Exception)
        {
            //If an exception occurs, it's because it's not in the list. Who cares?
        }
        finally
        {
            //Do nothing.
        }
    }

        

}

/// <summary>
/// For every showtime there will be made a list here. This list keep track on which seats are already taken by other customers.
/// </summary>
public class ReservedSeatsList
{    
    //This list is mase to make sure of which seats has been picked, to make sure it is possible for a user to select a seat.
    //Make sure the list is atomic!
    #region List of seats

    private static object _lock = new object();
        
    //This list (it's reference) should never be accessed by anyone else than the methids given in this class!
    private List<Tuple<Seat,DateTime>> _seats;
    private CtrTicket ctt = new CtrTicket();
    private ShowTime sht;

    public ReservedSeatsList(ShowTime showTime)
    {
        _seats = new List<Tuple<Seat, DateTime>>();
        sht = showTime;
        PopulateSeats();
    }

    public bool SeatIsTaken(Seat seat)
    {
        bool taken = false;
        try
        {
            Monitor.Enter(_lock);
            
            if (_seats.FindAll(x => (x.Item1.SeatNr == seat.SeatNr && x.Item1.Row == seat.Row && x.Item1.CinemaRoom == seat.CinemaRoom)).Count > 0)
            {
                    
                taken = true;
            }
        }
        finally
        {
            Monitor.Exit(_lock);
        }
        
        return taken;

    }
    public void AddSeat(Seat seat)
    {
        Monitor.Enter(_lock);
        try
        {
            if (!SeatIsTaken(seat) && sht.CinemaRoom.Id == seat.CinemaRoom)
            {
                _seats.Add(new Tuple<Seat, DateTime>(seat,DateTime.Now));
            }
        }
        finally
        {
            Monitor.Exit(_lock);
        }
    }

    public List<Seat> GetSeatList()
    {
        //If someone forgot to remove their seat, we need to make sure it's available again.
        CheckTimeStamp();
        //We always want the list to be updated!
        PopulateSeats();
        //Make sure to return a new list instead of the reference to the original.
        List<Seat> retList = _seats.Select(x => x.Item1).ToList();
        return retList;
    }

    public void RemoveSeat(Seat seat)
    {
        Monitor.Enter(_lock);
        try
        {
            _seats.Remove(_seats.Find(x => x.Item1.SeatNr == seat.SeatNr && x.Item1.Row == seat.Row && x.Item1.CinemaRoom == seat.CinemaRoom));
        }
        finally
        {
            Monitor.Exit(_lock);
        }
    }

    public void CheckTimeStamp()
    {
        Monitor.Enter(_lock);
        try
        {
            //How much time should people have to book their seats.
            _seats.Remove(_seats.Find(x => DateTime.Now.Subtract(x.Item2).Minutes > 5));
        }
        finally 
        {
            Monitor.Exit(_lock);
        }
    }

    public void PopulateSeats()
    {
        
        Monitor.Enter(_lock);
        try
        {
            List<Ticket> tickets = ctt.Read(sht);
            
            foreach (var ticket in tickets)
            {
                //We can't clear the list, as that would make it redundant every time a new client connects.
                //So we have to check the list manually. (We can't use contains, as they are different objects.)
                if (_seats.FindAll(x => x.Item1.SeatNr == ticket.Seat.SeatNr && x.Item1.Row == ticket.Seat.Row && x.Item1.CinemaRoom == ticket.Seat.CinemaRoom).Count < 1)
                {
                    this._seats.Add(new Tuple<Seat, DateTime>(ticket.Seat, ticket.ShowTime.DateTime));
                }
            }
        }
        catch(Exception e)
        {
            throw new Exception(e.Message);
        }
        finally
        {
            Monitor.Exit(_lock);
        }
    }

    #endregion
}
